/*
207. 课程表

你这个学期必须选修 numCourses 门课程，记为 0 到 numCourses - 1 。

在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出，其中 prerequisites[i] = [ai, bi] ，表示如果要学习课程 ai 则 必须 先学习课程  bi 。

例如，先修课程对 [0, 1] 表示：想要学习课程 0 ，你需要先完成课程 1 。
请你判断是否可能完成所有课程的学习？如果可以，返回 true ；否则，返回 false 。

*/
/**
 * @param {number} numCourses
 * @param {number[][]} prerequisites
 * @return {boolean}
 */
 var canFinish = function (numCourses, prerequisites) {
    // 有向图是否存在环
    let indexg = Array(numCourses).fill(0)  // 记录每个节点的入度
    let g = Array(numCourses).fill().map(() => [])  // 每个点指向的其他点的集合 
    let q = []  // 依次将入度为0的节点加到该队列
    // 遍历所有的边
    for (let [to, from] of prerequisites) {
        indexg[to] += 1;
        g[from].push(to)
    }
    // 收集所有入度为0的节点
    for (let i = 0; i < numCourses; i++) {
        // 入度为零，添加
        if (indexg[i] == 0) q.push(i)
    }
    let cnt = 0  // 拓扑序中的节点的数量
    // 队列不为空的情况下
    while (q.length) {
        let ind = q.shift()  // 弹出当前节点
        console.log('pop', ind)
        cnt++
        for (let to of g[ind]) {
            indexg[to]-- // 由于当前节点前驱节点被弹出，它的入度-1
            if (indexg[to] == 0) q.push(to) // 当入度为0时，添加到队列中
        }
    }
    // 能够弹出所有节点，则不存在环
    return cnt == numCourses
};